/*
This file is part of the iText (R) project.
Copyright (c) 1998-2025 Apryse Group NV
Authors: Apryse Software.

This program is offered under a commercial and under the AGPL license.
For commercial licensing, contact us at https://itextpdf.com/sales.  For AGPL licensing, see below.

AGPL licensing:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using iText.Commons;
using iText.Commons.Utils;
using iText.Forms.Exceptions;
using iText.Forms.Fields;
using iText.Forms.Fields.Merging;
using iText.Forms.Logs;
using iText.Forms.Xfa;
using iText.Kernel.Exceptions;
using iText.Kernel.Geom;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Annot;
using iText.Kernel.Pdf.Canvas;
using iText.Kernel.Pdf.Tagutils;
using iText.Kernel.Pdf.Xobject;

namespace iText.Forms {
    /// <summary>This class represents the static form technology AcroForm on a PDF file.</summary>
    public class PdfAcroForm : PdfObjectWrapper<PdfDictionary> {
        private static readonly ILogger LOGGER = ITextLogManager.GetLogger(typeof(iText.Forms.PdfAcroForm));

        /// <summary>
        /// To be used with
        /// <see cref="SetSignatureFlags(int)"/>.
        /// </summary>
        /// <remarks>
        /// To be used with
        /// <see cref="SetSignatureFlags(int)"/>.
        /// <br />
        /// <blockquote>
        /// If set, the document contains at least one signature field. This flag
        /// allows a conforming reader to enable user interface items (such as menu
        /// items or pushbuttons) related to signature processing without having to
        /// scan the entire document for the presence of signature fields.
        /// (ISO 32000-1, section 12.7.2 "Interactive Form Dictionary")
        /// </blockquote>
        /// </remarks>
        public const int SIGNATURE_EXIST = 1;

        /// <summary>
        /// To be used with
        /// <see cref="SetSignatureFlags(int)"/>.
        /// </summary>
        /// <remarks>
        /// To be used with
        /// <see cref="SetSignatureFlags(int)"/>.
        /// <br />
        /// <blockquote>
        /// If set, the document contains signatures that may be invalidated if the
        /// file is saved (written) in a way that alters its previous contents, as
        /// opposed to an incremental update. Merely updating the file by appending
        /// new information to the end of the previous version is safe. Conforming
        /// readers may use this flag to inform a user requesting a full save that
        /// signatures will be invalidated and require explicit confirmation before
        /// continuing with the operation.
        /// (ISO 32000-1, section 12.7.2 "Interactive Form Dictionary")
        /// </blockquote>
        /// </remarks>
        public const int APPEND_ONLY = 2;

        /// <summary>
        /// Keeps track of whether or not appearances must be generated by the form
        /// fields themselves, or by the PDF viewer application.
        /// </summary>
        /// <remarks>
        /// Keeps track of whether or not appearances must be generated by the form
        /// fields themselves, or by the PDF viewer application. Default is
        /// <c>true</c>.
        /// </remarks>
        protected internal bool generateAppearance = true;

        /// <summary>
        /// A map of field names and their associated
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// objects.
        /// </summary>
        protected internal IDictionary<String, PdfFormField> fields = new LinkedDictionary<String, PdfFormField>();

        /// <summary>The PdfDocument to which the PdfAcroForm belongs.</summary>
        protected internal PdfDocument document;

        private PdfDictionary defaultResources;

        private ICollection<PdfFormField> fieldsForFlattening = new LinkedHashSet<PdfFormField>();

        private XfaForm xfaForm;

        /// <summary>Creates a PdfAcroForm as a wrapper of a dictionary.</summary>
        /// <remarks>
        /// Creates a PdfAcroForm as a wrapper of a dictionary.
        /// Also initializes an XFA form if an <c>/XFA</c> entry is present in
        /// the dictionary.
        /// </remarks>
        /// <param name="pdfObject">the PdfDictionary to be wrapped</param>
        private PdfAcroForm(PdfDictionary pdfObject, PdfDocument pdfDocument)
            : base(pdfObject) {
            document = pdfDocument;
            fields = PopulateFormFieldsMap();
            xfaForm = new XfaForm(pdfObject);
        }

        /// <summary>
        /// Creates a PdfAcroForm from a
        /// <see cref="iText.Kernel.Pdf.PdfArray"/>
        /// of fields.
        /// </summary>
        /// <remarks>
        /// Creates a PdfAcroForm from a
        /// <see cref="iText.Kernel.Pdf.PdfArray"/>
        /// of fields.
        /// Also initializes an empty XFA form.
        /// </remarks>
        /// <param name="fields">
        /// a
        /// <see cref="iText.Kernel.Pdf.PdfArray"/>
        /// of
        /// <see cref="iText.Kernel.Pdf.PdfDictionary"/>
        /// objects
        /// </param>
        private PdfAcroForm(PdfArray fields)
            : this(CreateAcroFormDictionaryByFields(fields), null) {
            SetForbidRelease();
        }

        /// <summary>Retrieves AcroForm from the document.</summary>
        /// <remarks>
        /// Retrieves AcroForm from the document. If there is no AcroForm in the
        /// document Catalog and createIfNotExist flag is true then the AcroForm
        /// dictionary will be created and added to the document.
        /// </remarks>
        /// <param name="document">
        /// the document to retrieve the
        /// <see cref="PdfAcroForm"/>
        /// from
        /// </param>
        /// <param name="createIfNotExist">
        /// when <c>true</c>, this method will create a
        /// <see cref="PdfAcroForm"/>
        /// if none exists
        /// for this document
        /// </param>
        /// <returns>
        /// the
        /// <see cref="iText.Kernel.Pdf.PdfDocument">document</see>
        /// 's AcroForm,
        /// or a new one provided that <c>createIfNotExist</c> parameter is <c>true</c>, otherwise
        /// <c>null</c>.
        /// </returns>
        public static iText.Forms.PdfAcroForm GetAcroForm(PdfDocument document, bool createIfNotExist) {
            return GetAcroForm(document, createIfNotExist, new MergeFieldsStrategy());
        }

        /// <summary>Retrieves AcroForm from the document.</summary>
        /// <remarks>
        /// Retrieves AcroForm from the document. If there is no AcroForm in the
        /// document Catalog and createIfNotExist flag is true then the AcroForm
        /// dictionary will be created and added to the document.
        /// </remarks>
        /// <param name="document">
        /// the document to retrieve the
        /// <see cref="PdfAcroForm"/>
        /// from
        /// </param>
        /// <param name="createIfNotExist">
        /// when <c>true</c>, this method will create a
        /// <see cref="PdfAcroForm"/>
        /// if none
        /// exists for
        /// this document
        /// </param>
        /// <param name="onDuplicateFieldNameStrategy">the strategy to be used when a field with the same name already exists
        ///     </param>
        /// <returns>
        /// the
        /// <see cref="iText.Kernel.Pdf.PdfDocument">document</see>
        /// 's AcroForm,
        /// or a new one provided that <c>createIfNotExist</c> parameter is <c>true</c>, otherwise
        /// <c>null</c>.
        /// </returns>
        public static iText.Forms.PdfAcroForm GetAcroForm(PdfDocument document, bool createIfNotExist, OnDuplicateFormFieldNameStrategy
             onDuplicateFieldNameStrategy) {
            document.GetDiContainer().Register(typeof(OnDuplicateFormFieldNameStrategy), onDuplicateFieldNameStrategy);
            PdfDictionary acroFormDictionary = document.GetCatalog().GetPdfObject().GetAsDictionary(PdfName.AcroForm);
            iText.Forms.PdfAcroForm acroForm = null;
            if (acroFormDictionary == null) {
                if (createIfNotExist) {
                    acroForm = new iText.Forms.PdfAcroForm(new PdfArray());
                    acroForm.MakeIndirect(document);
                    document.GetCatalog().Put(PdfName.AcroForm, acroForm.GetPdfObject());
                    document.GetCatalog().SetModified();
                }
            }
            else {
                acroForm = new iText.Forms.PdfAcroForm(acroFormDictionary, document);
            }
            if (acroForm != null) {
                acroForm.defaultResources = acroForm.GetDefaultResources();
                if (acroForm.defaultResources == null) {
                    acroForm.defaultResources = new PdfDictionary();
                }
                acroForm.document = document;
                acroForm.xfaForm = new XfaForm(document);
            }
            return acroForm;
        }

        /// <summary>This method adds the field to the last page in the document.</summary>
        /// <remarks>
        /// This method adds the field to the last page in the document.
        /// If there's no pages, creates a new one.
        /// </remarks>
        /// <param name="field">
        /// the
        /// <see cref="iText.Forms.Fields.PdfFormField"/>
        /// to be added to the form
        /// </param>
        public virtual void AddField(PdfFormField field) {
            if (!field.GetPdfObject().ContainsKey(PdfName.T)) {
                throw new PdfException(FormsExceptionMessageConstant.FORM_FIELD_MUST_HAVE_A_NAME);
            }
            PdfPage page;
            if (document.GetNumberOfPages() == 0) {
                document.AddNewPage();
            }
            page = document.GetLastPage();
            AddField(field, page);
        }

        /// <summary>This method adds the field to a specific page.</summary>
        /// <param name="field">
        /// the
        /// <see cref="iText.Forms.Fields.PdfFormField"/>
        /// to be added to the form
        /// </param>
        /// <param name="page">
        /// the
        /// <see cref="iText.Kernel.Pdf.PdfPage"/>
        /// on which to add the field
        /// </param>
        public virtual void AddField(PdfFormField field, PdfPage page) {
            AddField(field, page, true);
        }

        /// <summary>This method adds the field to a specific page.</summary>
        /// <param name="field">
        /// the
        /// <see cref="iText.Forms.Fields.PdfFormField"/>
        /// to be added to the form
        /// </param>
        /// <param name="page">
        /// the
        /// <see cref="iText.Kernel.Pdf.PdfPage"/>
        /// on which to add the field
        /// </param>
        /// <param name="throwExceptionOnError">true if the exception is expected to be thrown in case of error.</param>
        public virtual void AddField(PdfFormField field, PdfPage page, bool throwExceptionOnError) {
            if (!field.GetPdfObject().ContainsKey(PdfName.T)) {
                if (throwExceptionOnError) {
                    throw new PdfException(FormsExceptionMessageConstant.FORM_FIELD_MUST_HAVE_A_NAME);
                }
                else {
                    LOGGER.LogWarning(FormsLogMessageConstants.FORM_FIELD_MUST_HAVE_A_NAME);
                    return;
                }
            }
            PdfFormFieldMergeUtil.MergeKidsWithSameNames(field, throwExceptionOnError);
            // PdfPageFormCopier expects that we replace existed field by a new one in case they have the same names.
            if (NeedToAddToAcroform(field, throwExceptionOnError)) {
                PdfArray fieldsArray = GetFields();
                fieldsArray.Add(field.GetPdfObject());
                fieldsArray.SetModified();
                fields.Put(field.GetFieldName().ToUnicodeString(), field);
            }
            PdfDictionary fieldDict = field.GetPdfObject();
            ProcessKids(fields.Get(field.GetFieldName().ToUnicodeString()), page);
            if (fieldDict.ContainsKey(PdfName.Subtype) && page != null) {
                DefineWidgetPageAndAddToIt(page, fieldDict, false);
            }
            SetModified();
        }

        /// <summary>
        /// This method merges field with its annotation and places it on the given
        /// page.
        /// </summary>
        /// <remarks>
        /// This method merges field with its annotation and places it on the given
        /// page. This method also work if the field has more than one widget
        /// annotation, but doesn't work with no annotations.
        /// </remarks>
        /// <param name="field">
        /// the
        /// <see cref="iText.Forms.Fields.PdfFormField"/>
        /// to be added to the form
        /// </param>
        /// <param name="page">
        /// the
        /// <see cref="iText.Kernel.Pdf.PdfPage"/>
        /// on which to add the field
        /// </param>
        public virtual void AddFieldAppearanceToPage(PdfFormField field, PdfPage page) {
            PdfDictionary fieldDict = field.GetPdfObject();
            PdfArray kids = field.GetKids();
            if (kids == null) {
                return;
            }
            if (kids.Size() == 1) {
                PdfDictionary kidDict = (PdfDictionary)kids.Get(0);
                if (PdfFormAnnotationUtil.IsPureWidget(kidDict)) {
                    // kid is pure widget, merge it with parent field
                    PdfFormAnnotationUtil.MergeWidgetWithParentField(field);
                    DefineWidgetPageAndAddToIt(page, fieldDict, false);
                    return;
                }
            }
            for (int i = 0; i < kids.Size(); ++i) {
                PdfDictionary kidDict = (PdfDictionary)kids.Get(i);
                if (PdfFormAnnotationUtil.IsPureWidgetOrMergedField(kidDict)) {
                    // kid is either a pure widget or a merged field
                    DefineWidgetPageAndAddToIt(page, kidDict, false);
                }
            }
        }

        /// <summary>Gets root fields (i.e. direct children of Acroform dictionary).</summary>
        /// <returns>
        /// a map of field names and their associated
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// objects
        /// </returns>
        public virtual IDictionary<String, PdfFormField> GetRootFormFields() {
            if (fields.Count == 0) {
                fields = PopulateFormFieldsMap();
            }
            //TODO DEVSIX-6504 Fix copyField logic.
            return fields;
        }

        /// <summary>
        /// Gets all
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// s as a
        /// <see cref="System.Collections.IDictionary{K, V}"/>
        /// including fields kids.
        /// </summary>
        /// <returns>
        /// a map of field names and their associated
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// objects
        /// </returns>
        public virtual IDictionary<String, PdfFormField> GetAllFormFields() {
            if (fields.IsEmpty()) {
                fields = PopulateFormFieldsMap();
            }
            IDictionary<String, PdfFormField> allFields = new LinkedDictionary<String, PdfFormField>(fields);
            foreach (KeyValuePair<String, PdfFormField> field in fields) {
                IList<PdfFormField> kids = field.Value.GetAllChildFormFields();
                foreach (PdfFormField kid in kids) {
                    PdfString kidFieldName = kid.GetFieldName();
                    if (kidFieldName != null) {
                        allFields.Put(kidFieldName.ToUnicodeString(), kid);
                    }
                }
            }
            return allFields;
        }

        /// <summary>
        /// Gets all
        /// <see cref="iText.Forms.Fields.AbstractPdfFormField">form field</see>
        /// s as a
        /// <see cref="Java.Util.Set{E}"/>
        /// including fields kids and nameless fields.
        /// </summary>
        /// <returns>
        /// a set of
        /// <see cref="iText.Forms.Fields.AbstractPdfFormField">form field</see>
        /// objects.
        /// </returns>
        public virtual ICollection<AbstractPdfFormField> GetAllFormFieldsAndAnnotations() {
            if (fields.IsEmpty()) {
                fields = PopulateFormFieldsMap();
            }
            ICollection<AbstractPdfFormField> allFields = new LinkedHashSet<AbstractPdfFormField>();
            foreach (KeyValuePair<String, PdfFormField> field in fields) {
                allFields.Add(field.Value);
                IList<AbstractPdfFormField> kids = field.Value.GetAllChildFields();
                allFields.AddAll(kids);
            }
            return allFields;
        }

        /// <summary>
        /// Gets a collection of
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// s, prepared for flattening using
        /// <see cref="PartialFormFlattening(System.String)"/>
        /// method.
        /// </summary>
        /// <remarks>
        /// Gets a collection of
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// s, prepared for flattening using
        /// <see cref="PartialFormFlattening(System.String)"/>
        /// method.
        /// If returned collection is empty, all form fields will be flattened on
        /// <see cref="FlattenFields()">flattenFields</see>
        /// call.
        /// </remarks>
        /// <returns>
        /// a collection of
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// s for flattening
        /// </returns>
        public virtual ICollection<PdfFormField> GetFieldsForFlattening() {
            return JavaCollectionsUtil.UnmodifiableCollection(fieldsForFlattening);
        }

        /// <summary>
        /// Gets the
        /// <see cref="iText.Kernel.Pdf.PdfDocument"/>
        /// this
        /// <see cref="PdfAcroForm"/>
        /// belongs to.
        /// </summary>
        /// <returns>the document of this form</returns>
        public virtual PdfDocument GetPdfDocument() {
            return document;
        }

        /// <summary>Sets the <c>NeedAppearances</c> boolean property on the AcroForm.</summary>
        /// <remarks>
        /// Sets the <c>NeedAppearances</c> boolean property on the AcroForm.
        /// NeedAppearances has been deprecated in PDF 2.0.
        /// <br />
        /// <blockquote>
        /// NeedAppearances is a flag specifying whether to construct appearance
        /// streams and appearance dictionaries for all widget annotations in the
        /// document.
        /// (ISO 32000-1, section 12.7.2 "Interactive Form Dictionary")
        /// </blockquote>
        /// </remarks>
        /// <param name="needAppearances">a boolean. Default value is <c>false</c></param>
        /// <returns>current AcroForm.</returns>
        public virtual iText.Forms.PdfAcroForm SetNeedAppearances(bool needAppearances) {
            if (VersionConforming.ValidatePdfVersionForDeprecatedFeatureLogError(document, PdfVersion.PDF_2_0, VersionConforming
                .DEPRECATED_NEED_APPEARANCES_IN_ACROFORM)) {
                GetPdfObject().Remove(PdfName.NeedAppearances);
                SetModified();
            }
            else {
                Put(PdfName.NeedAppearances, PdfBoolean.ValueOf(needAppearances));
            }
            return this;
        }

        /// <summary>Gets the <c>NeedAppearances</c> boolean property on the AcroForm.</summary>
        /// <remarks>
        /// Gets the <c>NeedAppearances</c> boolean property on the AcroForm.
        /// NeedAppearances has been deprecated in PDF 2.0.
        /// <br />
        /// <blockquote>
        /// NeedAppearances is a flag specifying whether to construct appearance
        /// streams and appearance dictionaries for all widget annotations in the
        /// document.
        /// (ISO 32000-1, section 12.7.2 "Interactive Form Dictionary")
        /// </blockquote>
        /// </remarks>
        /// <returns>
        /// the <c>NeedAppearances</c> property as a
        /// <see cref="iText.Kernel.Pdf.PdfBoolean"/>
        /// . Default value is <c>false</c>
        /// </returns>
        public virtual PdfBoolean GetNeedAppearances() {
            return GetPdfObject().GetAsBoolean(PdfName.NeedAppearances);
        }

        /// <summary>Sets the <c>SigFlags</c> integer property on the AcroForm.</summary>
        /// <remarks>
        /// Sets the <c>SigFlags</c> integer property on the AcroForm.
        /// <br />
        /// <blockquote>
        /// SigFlags is a set of flags specifying various document-level
        /// characteristics related to signature fields.
        /// (ISO 32000-1, section 12.7.2 "Interactive Form Dictionary")
        /// </blockquote>
        /// </remarks>
        /// <param name="sigFlags">
        /// an integer. Use
        /// <see cref="SIGNATURE_EXIST"/>
        /// and/or
        /// <see cref="APPEND_ONLY"/>.
        /// Use bitwise OR operator to combine these values. Default value is <c>0</c>
        /// </param>
        /// <returns>current AcroForm.</returns>
        public virtual iText.Forms.PdfAcroForm SetSignatureFlags(int sigFlags) {
            return Put(PdfName.SigFlags, new PdfNumber(sigFlags));
        }

        /// <summary>Changes the <c>SigFlags</c> integer property on the AcroForm.</summary>
        /// <remarks>
        /// Changes the <c>SigFlags</c> integer property on the AcroForm.
        /// This method allows only to add flags, not to remove them.
        /// <br />
        /// <blockquote>
        /// SigFlags is a set of flags specifying various document-level
        /// characteristics related to signature fields.
        /// (ISO 32000-1, section 12.7.2 "Interactive Form Dictionary")
        /// </blockquote>
        /// </remarks>
        /// <param name="sigFlag">
        /// an integer. Use
        /// <see cref="SIGNATURE_EXIST"/>
        /// and/or
        /// <see cref="APPEND_ONLY"/>.
        /// Use bitwise OR operator to combine these values. Default is <c>0</c>
        /// </param>
        /// <returns>current AcroForm.</returns>
        public virtual iText.Forms.PdfAcroForm SetSignatureFlag(int sigFlag) {
            int flags = GetSignatureFlags();
            flags = flags | sigFlag;
            return SetSignatureFlags(flags);
        }

        /// <summary>Gets the <c>SigFlags</c> integer property on the AcroForm.</summary>
        /// <remarks>
        /// Gets the <c>SigFlags</c> integer property on the AcroForm.
        /// <br />
        /// <blockquote>
        /// SigFlags is a set of flags specifying various document-level
        /// characteristics related to signature fields
        /// (ISO 32000-1, section 12.7.2 "Interactive Form Dictionary")
        /// </blockquote>
        /// </remarks>
        /// <returns>current value for <c>SigFlags</c>.</returns>
        public virtual int GetSignatureFlags() {
            PdfNumber f = GetPdfObject().GetAsNumber(PdfName.SigFlags);
            if (f == null) {
                return 0;
            }
            else {
                return f.IntValue();
            }
        }

        /// <summary>Sets the <c>CO</c> array property on the AcroForm.</summary>
        /// <remarks>
        /// Sets the <c>CO</c> array property on the AcroForm.
        /// <br />
        /// <blockquote>
        /// <c>CO</c>, Calculation Order, is an array of indirect references to
        /// field dictionaries with calculation actions, defining the calculation
        /// order in which their values will be recalculated when the value of any
        /// field changes
        /// (ISO 32000-1, section 12.7.2 "Interactive Form Dictionary")
        /// </blockquote>
        /// </remarks>
        /// <param name="calculationOrder">an array of indirect references</param>
        /// <returns>current AcroForm</returns>
        public virtual iText.Forms.PdfAcroForm SetCalculationOrder(PdfArray calculationOrder) {
            return Put(PdfName.CO, calculationOrder);
        }

        /// <summary>Gets the <c>CO</c> array property on the AcroForm.</summary>
        /// <remarks>
        /// Gets the <c>CO</c> array property on the AcroForm.
        /// <br />
        /// <blockquote>
        /// <c>CO</c>, Calculation Order, is an array of indirect references to
        /// field dictionaries with calculation actions, defining the calculation
        /// order in which their values will be recalculated when the value of any
        /// field changes
        /// (ISO 32000-1, section 12.7.2 "Interactive Form Dictionary")
        /// </blockquote>
        /// </remarks>
        /// <returns>an array of indirect references</returns>
        public virtual PdfArray GetCalculationOrder() {
            return GetPdfObject().GetAsArray(PdfName.CO);
        }

        /// <summary>Sets the <c>DR</c> dictionary property on the AcroForm.</summary>
        /// <remarks>
        /// Sets the <c>DR</c> dictionary property on the AcroForm.
        /// <br />
        /// <blockquote>
        /// <c>DR</c> is a resource dictionary containing default resources
        /// (such as fonts, patterns, or colour spaces) that shall be used by form
        /// field appearance streams. At a minimum, this dictionary shall contain a
        /// Font entry specifying the resource name and font dictionary of the
        /// default font for displaying text.
        /// (ISO 32000-1, section 12.7.2 "Interactive Form Dictionary")
        /// </blockquote>
        /// </remarks>
        /// <param name="defaultResources">a resource dictionary</param>
        /// <returns>current AcroForm</returns>
        public virtual iText.Forms.PdfAcroForm SetDefaultResources(PdfDictionary defaultResources) {
            return Put(PdfName.DR, defaultResources);
        }

        /// <summary>Gets the <c>DR</c> dictionary property on the AcroForm.</summary>
        /// <remarks>
        /// Gets the <c>DR</c> dictionary property on the AcroForm.
        /// <br />
        /// <blockquote>
        /// <c>DR</c> is a resource dictionary containing default resources
        /// (such as fonts, patterns, or colour spaces) that shall be used by form
        /// field appearance streams. At a minimum, this dictionary shall contain a
        /// Font entry specifying the resource name and font dictionary of the
        /// default font for displaying text.
        /// (ISO 32000-1, section 12.7.2 "Interactive Form Dictionary")
        /// </blockquote>
        /// </remarks>
        /// <returns>a resource dictionary</returns>
        public virtual PdfDictionary GetDefaultResources() {
            return GetPdfObject().GetAsDictionary(PdfName.DR);
        }

        /// <summary>Sets the <c>DA</c> String property on the AcroForm.</summary>
        /// <remarks>
        /// Sets the <c>DA</c> String property on the AcroForm.
        /// <br />
        /// This method sets a default (fallback value) for the <c>DA</c>
        /// attribute of variable text
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// s.
        /// </remarks>
        /// <param name="appearance">a String containing a sequence of valid PDF syntax</param>
        /// <returns>current AcroForm</returns>
        public virtual iText.Forms.PdfAcroForm SetDefaultAppearance(String appearance) {
            return Put(PdfName.DA, new PdfString(appearance));
        }

        /// <summary>Gets the <c>DA</c> String property on the AcroForm.</summary>
        /// <remarks>
        /// Gets the <c>DA</c> String property on the AcroForm.
        /// <br />
        /// This method returns the default (fallback value) for the <c>DA</c>
        /// attribute of variable text
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// s.
        /// </remarks>
        /// <returns>the form-wide default appearance, as a <c>String</c></returns>
        public virtual PdfString GetDefaultAppearance() {
            return GetPdfObject().GetAsString(PdfName.DA);
        }

        /// <summary>Sets the <c>Q</c> integer property on the AcroForm.</summary>
        /// <remarks>
        /// Sets the <c>Q</c> integer property on the AcroForm.
        /// <br />
        /// This method sets a default (fallback value) for the <c>Q</c>
        /// attribute of variable text
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// s.
        /// </remarks>
        /// <param name="justification">an integer representing a justification value</param>
        /// <returns>current AcroForm</returns>
        /// <seealso cref="iText.Forms.Fields.PdfFormField.SetJustification(iText.Layout.Properties.TextAlignment?)"/>
        public virtual iText.Forms.PdfAcroForm SetDefaultJustification(int justification) {
            return Put(PdfName.Q, new PdfNumber(justification));
        }

        /// <summary>Gets the <c>Q</c> integer property on the AcroForm.</summary>
        /// <remarks>
        /// Gets the <c>Q</c> integer property on the AcroForm.
        /// <br />
        /// This method gets the default (fallback value) for the <c>Q</c>
        /// attribute of variable text
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// s.
        /// </remarks>
        /// <returns>an integer representing a justification value</returns>
        /// <seealso cref="iText.Forms.Fields.PdfFormField.GetJustification()"/>
        public virtual PdfNumber GetDefaultJustification() {
            return GetPdfObject().GetAsNumber(PdfName.Q);
        }

        /// <summary>Sets the <c>XFA</c> property on the AcroForm.</summary>
        /// <remarks>
        /// Sets the <c>XFA</c> property on the AcroForm.
        /// <br />
        /// <c>XFA</c> can either be a
        /// <see cref="iText.Kernel.Pdf.PdfStream"/>
        /// or a
        /// <see cref="iText.Kernel.Pdf.PdfArray"/>.
        /// Its contents must be valid XFA.
        /// </remarks>
        /// <param name="xfaResource">a stream containing the XDP</param>
        /// <returns>current AcroForm</returns>
        public virtual iText.Forms.PdfAcroForm SetXFAResource(PdfStream xfaResource) {
            return Put(PdfName.XFA, xfaResource);
        }

        /// <summary>Sets the <c>XFA</c> property on the AcroForm.</summary>
        /// <remarks>
        /// Sets the <c>XFA</c> property on the AcroForm.
        /// <br />
        /// <c>XFA</c> can either be a
        /// <see cref="iText.Kernel.Pdf.PdfStream"/>
        /// or a
        /// <see cref="iText.Kernel.Pdf.PdfArray"/>.
        /// Its contents must be valid XFA.
        /// </remarks>
        /// <param name="xfaResource">
        /// an array of text string and stream pairs representing
        /// the individual packets comprising the XML Data Package. (ISO 32000-1,
        /// section 12.7.2 "Interactive Form Dictionary")
        /// </param>
        /// <returns>current AcroForm</returns>
        public virtual iText.Forms.PdfAcroForm SetXFAResource(PdfArray xfaResource) {
            return Put(PdfName.XFA, xfaResource);
        }

        /// <summary>Gets the <c>XFA</c> property on the AcroForm.</summary>
        /// <returns>
        /// an object representing the entire XDP. It can either be a
        /// <see cref="iText.Kernel.Pdf.PdfStream"/>
        /// or a
        /// <see cref="iText.Kernel.Pdf.PdfArray"/>.
        /// </returns>
        public virtual PdfObject GetXFAResource() {
            return GetPdfObject().Get(PdfName.XFA);
        }

        /// <summary>
        /// Gets a
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// by its name.
        /// </summary>
        /// <param name="fieldName">
        /// the name of the
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// to retrieve
        /// </param>
        /// <returns>
        /// the
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// , or <c>null</c> if it
        /// isn't present
        /// </returns>
        public virtual PdfFormField GetField(String fieldName) {
            if (fields.Get(fieldName) != null) {
                return fields.Get(fieldName);
            }
            String[] splitFieldsArray = StringSplitUtil.SplitKeepTrailingWhiteSpace(fieldName, '.');
            if (splitFieldsArray.Length == 0) {
                return null;
            }
            PdfFormField parentFormField = fields.Get(splitFieldsArray[0]);
            PdfFormField kidField = parentFormField;
            for (int i = 1; i < splitFieldsArray.Length; i++) {
                if (parentFormField == null || parentFormField.IsFlushed()) {
                    return null;
                }
                kidField = parentFormField.GetChildField(splitFieldsArray[i]);
                parentFormField = kidField;
            }
            return kidField;
        }

        /// <summary>
        /// Gets the attribute generateAppearance, which tells
        /// <see cref="FlattenFields()"/>
        /// to generate an appearance Stream for all
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// s
        /// that don't have one.
        /// </summary>
        /// <returns>bolean value indicating if the appearances need to be generated</returns>
        public virtual bool IsGenerateAppearance() {
            return generateAppearance;
        }

        /// <summary>
        /// Sets the attribute generateAppearance, which tells
        /// <see cref="FlattenFields()"/>
        /// to generate an appearance Stream for all
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// s
        /// that don't have one.
        /// </summary>
        /// <remarks>
        /// Sets the attribute generateAppearance, which tells
        /// <see cref="FlattenFields()"/>
        /// to generate an appearance Stream for all
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// s
        /// that don't have one.
        /// <para />
        /// Not generating appearances will speed up form flattening but the results
        /// can be unexpected in Acrobat. Don't use it unless your environment is
        /// well controlled. The default is <c>true</c>.
        /// <para />
        /// If generateAppearance is set to <c>true</c>, then
        /// <c>NeedAppearances</c> is set to <c>false</c>. This does not
        /// apply vice versa.
        /// <para />
        /// Note, this method does not change default behaviour of
        /// <see cref="iText.Forms.Fields.PdfFormField.SetValue(System.String)"/>
        /// method.
        /// </remarks>
        /// <param name="generateAppearance">a boolean</param>
        public virtual void SetGenerateAppearance(bool generateAppearance) {
            if (generateAppearance) {
                GetPdfObject().Remove(PdfName.NeedAppearances);
                SetModified();
            }
            this.generateAppearance = generateAppearance;
        }

        /// <summary>
        /// Flattens interactive
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// s in the document.
        /// </summary>
        /// <remarks>
        /// Flattens interactive
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// s in the document. If
        /// no fields have been explicitly included via
        /// <see cref="PartialFormFlattening(System.String)"/>
        /// ,
        /// then all fields are flattened. Otherwise only the included fields are
        /// flattened.
        /// </remarks>
        public virtual void FlattenFields() {
            if (document.IsAppendMode()) {
                throw new PdfException(FormsExceptionMessageConstant.FIELD_FLATTENING_IS_NOT_SUPPORTED_IN_APPEND_MODE);
            }
            ICollection<PdfFormField> fields;
            if (fieldsForFlattening.IsEmpty()) {
                this.fields.Clear();
                fields = GetAllFormFieldsWithoutNames();
            }
            else {
                fields = new LinkedHashSet<PdfFormField>();
                foreach (PdfFormField field in fieldsForFlattening) {
                    fields.AddAll(PrepareFieldsForFlattening(field));
                }
            }
            // In case of appearance resources and page resources are the same object, it would not be possible to add
            // the xObject to the page resources. So in that case we would copy page resources and use the copy for
            // xObject, so that circular reference is avoided.
            // We copy beforehand firstly not to produce a copy every time, and secondly not to copy all the
            // xObjects that have already been added to the page resources.
            IDictionary<int, PdfObject> initialPageResourceClones = new LinkedDictionary<int, PdfObject>();
            for (int i = 1; i <= document.GetNumberOfPages(); i++) {
                PdfObject resources = document.GetPage(i).GetPdfObject().GetAsDictionary(PdfName.Resources);
                initialPageResourceClones.Put(i, resources == null ? null : resources.Clone());
            }
            ICollection<PdfPage> wrappedPages = new LinkedHashSet<PdfPage>();
            PdfPage page;
            foreach (PdfFormField formField in fields) {
                foreach (PdfFormAnnotation fieldAnnot in formField.GetChildFormAnnotations()) {
                    PdfDictionary fieldObject = fieldAnnot.GetPdfObject();
                    page = GetFieldPage(fieldObject);
                    if (page == null) {
                        continue;
                    }
                    PdfAnnotation annotation = PdfAnnotation.MakeAnnotation(fieldObject);
                    TagTreePointer tagPointer = null;
                    if (annotation != null && document.IsTagged()) {
                        tagPointer = document.GetTagStructureContext().RemoveAnnotationTag(annotation);
                    }
                    PdfDictionary appDic = fieldObject.GetAsDictionary(PdfName.AP);
                    PdfObject asNormal = null;
                    if (appDic != null) {
                        asNormal = appDic.GetAsStream(PdfName.N);
                        if (asNormal == null) {
                            asNormal = appDic.GetAsDictionary(PdfName.N);
                        }
                    }
                    if (generateAppearance) {
                        if (appDic == null || asNormal == null) {
                            fieldAnnot.RegenerateField();
                            appDic = fieldObject.GetAsDictionary(PdfName.AP);
                        }
                    }
                    PdfObject normal = appDic != null ? appDic.Get(PdfName.N) : null;
                    if (null != normal) {
                        PdfFormXObject xObject = null;
                        if (normal.IsStream()) {
                            xObject = new PdfFormXObject((PdfStream)normal);
                        }
                        else {
                            if (normal.IsDictionary()) {
                                PdfName @as = fieldObject.GetAsName(PdfName.AS);
                                if (((PdfDictionary)normal).GetAsStream(@as) != null) {
                                    xObject = new PdfFormXObject(((PdfDictionary)normal).GetAsStream(@as));
                                    xObject.MakeIndirect(document);
                                }
                            }
                        }
                        if (xObject != null) {
                            //subtype is required field for FormXObject, but can be omitted in normal appearance.
                            xObject.Put(PdfName.Subtype, PdfName.Form);
                            Rectangle annotBBox = fieldObject.GetAsRectangle(PdfName.Rect);
                            if (page.IsFlushed()) {
                                throw new PdfException(FormsExceptionMessageConstant.PAGE_ALREADY_FLUSHED_USE_ADD_FIELD_APPEARANCE_TO_PAGE_METHOD_BEFORE_PAGE_FLUSHING
                                    );
                            }
                            PdfCanvas canvas = new PdfCanvas(page, !wrappedPages.Contains(page));
                            wrappedPages.Add(page);
                            // Here we avoid circular reference which might occur when page resources and the appearance xObject's
                            // resources are the same object
                            PdfObject xObjectResources = xObject.GetPdfObject().Get(PdfName.Resources);
                            PdfObject pageResources = page.GetResources().GetPdfObject();
                            if (xObjectResources != null && xObjectResources == pageResources) {
                                xObject.GetPdfObject().Put(PdfName.Resources, initialPageResourceClones.Get(document.GetPageNumber(page)));
                            }
                            if (tagPointer != null) {
                                tagPointer.SetPageForTagging(page);
                                TagReference tagRef = tagPointer.GetTagReference();
                                canvas.OpenTag(tagRef);
                            }
                            AffineTransform at = PdfFormXObject.CalcAppearanceTransformToAnnotRect(xObject, annotBBox);
                            float[] m = new float[6];
                            at.GetMatrix(m);
                            canvas.AddXObjectWithTransformationMatrix(xObject, m[0], m[1], m[2], m[3], m[4], m[5]);
                            if (tagPointer != null) {
                                canvas.CloseTag();
                            }
                        }
                    }
                    else {
                        LOGGER.LogWarning(FormsLogMessageConstants.N_ENTRY_IS_REQUIRED_FOR_APPEARANCE_DICTIONARY);
                    }
                    PdfArray fFields = GetFields();
                    if (annotation != null) {
                        page.RemoveAnnotation(annotation);
                    }
                    RemoveFieldFromParentAndAcroForm(fFields, fieldObject);
                }
            }
            GetPdfObject().Remove(PdfName.NeedAppearances);
            if (fieldsForFlattening.Count == 0) {
                GetFields().Clear();
            }
            if (GetFields().IsEmpty()) {
                document.GetCatalog().Remove(PdfName.AcroForm);
            }
        }

        /// <summary>
        /// Tries to remove the
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// with the specified
        /// name from the document.
        /// </summary>
        /// <param name="fieldName">
        /// the name of the
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// to remove
        /// </param>
        /// <returns>a boolean representing whether or not the removal succeeded.</returns>
        public virtual bool RemoveField(String fieldName) {
            PdfFormField field = GetField(fieldName);
            if (field == null) {
                return false;
            }
            PdfDictionary fieldObject = field.GetPdfObject();
            PdfPage page = GetFieldPage(fieldObject);
            PdfAnnotation annotation = PdfAnnotation.MakeAnnotation(fieldObject);
            if (page != null && annotation != null) {
                page.RemoveAnnotation(annotation);
            }
            PdfDictionary parent = field.GetParent();
            PdfFormField parentField = field.GetParentField();
            if (parent != null) {
                PdfArray kids = parent.GetAsArray(PdfName.Kids);
                if (parentField != null) {
                    parentField.RemoveChild(field);
                }
                kids.Remove(fieldObject);
                kids.SetModified();
                parent.SetModified();
                return true;
            }
            PdfArray fieldsPdfArray = GetFields();
            if (fieldsPdfArray.Contains(fieldObject)) {
                fieldsPdfArray.Remove(fieldObject);
                this.fields.JRemove(fieldName);
                fieldsPdfArray.SetModified();
                SetModified();
                return true;
            }
            return false;
        }

        /// <summary>
        /// Adds a
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// , identified by name, to the list of fields to be flattened.
        /// </summary>
        /// <remarks>
        /// Adds a
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// , identified by name, to the list of fields to be flattened.
        /// Does not perform a flattening operation in itself.
        /// </remarks>
        /// <param name="fieldName">
        /// the name of the
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// to be flattened
        /// </param>
        public virtual void PartialFormFlattening(String fieldName) {
            PdfFormField field = GetAllFormFields().Get(fieldName);
            if (field != null) {
                fieldsForFlattening.Add(field);
            }
        }

        /// <summary>
        /// Changes the identifier of a
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>.
        /// </summary>
        /// <param name="oldName">the current name of the field</param>
        /// <param name="newName">the new name of the field. Must not be used currently.</param>
        public virtual void RenameField(String oldName, String newName) {
            PdfFormField oldField = GetField(oldName);
            if (oldField == null) {
                LOGGER.LogWarning(MessageFormatUtil.Format(FormsLogMessageConstants.FIELDNAME_NOT_FOUND_OPERATION_CAN_NOT_BE_COMPLETED
                    , oldName));
                return;
            }
            GetField(oldName).SetFieldName(newName);
            PdfFormField field = fields.Get(oldName);
            if (field != null) {
                fields.JRemove(oldName);
                fields.Put(newName, field);
            }
        }

        /// <summary>
        /// Creates an in-memory copy of a
        /// <see cref="iText.Forms.Fields.PdfFormField"/>.
        /// </summary>
        /// <remarks>
        /// Creates an in-memory copy of a
        /// <see cref="iText.Forms.Fields.PdfFormField"/>
        /// . This new field is
        /// not added to the document.
        /// </remarks>
        /// <param name="name">
        /// the name of the
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// to be copied
        /// </param>
        /// <returns>
        /// a clone of the original
        /// <see cref="iText.Forms.Fields.PdfFormField"/>
        /// </returns>
        public virtual PdfFormField CopyField(String name) {
            PdfFormField oldField = GetField(name);
            if (oldField != null) {
                return PdfFormCreator.CreateFormField((PdfDictionary)oldField.GetPdfObject().Clone().MakeIndirect(document
                    ));
            }
            return null;
        }

        /// <summary>
        /// Replaces the
        /// <see cref="iText.Forms.Fields.PdfFormField"/>
        /// of a certain name with another
        /// <see cref="iText.Forms.Fields.PdfFormField"/>.
        /// </summary>
        /// <param name="name">
        /// the name of the
        /// <see cref="iText.Forms.Fields.PdfFormField">form field</see>
        /// to be replaced
        /// </param>
        /// <param name="field">
        /// the new
        /// <see cref="iText.Forms.Fields.PdfFormField"/>
        /// </param>
        public virtual void ReplaceField(String name, PdfFormField field) {
            if (name == null) {
                LOGGER.LogWarning(FormsLogMessageConstants.PROVIDE_FORMFIELD_NAME);
                return;
            }
            RemoveField(name);
            int lastDotIndex = name.LastIndexOf('.');
            if (lastDotIndex == -1) {
                AddField(field);
                return;
            }
            String parentName = name.JSubstring(0, lastDotIndex);
            PdfFormField parent = GetField(parentName);
            if (parent == null) {
                AddField(field);
            }
            else {
                parent.AddKid(field);
            }
        }

        /// <summary>
        /// Disables appearance stream regeneration for all the root fields in the Acroform, so all of its children
        /// in the hierarchy will also not be regenerated.
        /// </summary>
        public virtual void DisableRegenerationForAllFields() {
            foreach (PdfFormField rootField in GetRootFormFields().Values) {
                rootField.DisableFieldRegeneration();
            }
        }

        /// <summary>Enables appearance stream regeneration for all the fields in the Acroform and regenerates them.</summary>
        public virtual void EnableRegenerationForAllFields() {
            foreach (PdfFormField rootField in GetRootFormFields().Values) {
                rootField.EnableFieldRegeneration();
            }
        }

        /// <summary>Gets all AcroForm fields in the document.</summary>
        /// <returns>
        /// a
        /// <see cref="iText.Kernel.Pdf.PdfArray"/>
        /// of field dictionaries
        /// </returns>
        protected internal virtual PdfArray GetFields() {
            PdfArray fields = GetPdfObject().GetAsArray(PdfName.Fields);
            if (fields == null) {
                LOGGER.LogWarning(FormsLogMessageConstants.NO_FIELDS_IN_ACROFORM);
                fields = new PdfArray();
                GetPdfObject().Put(PdfName.Fields, fields);
            }
            return fields;
        }

        protected override bool IsWrappedObjectMustBeIndirect() {
            return false;
        }

        private IDictionary<String, PdfFormField> PopulateFormFieldsMap() {
            PdfArray rawFields = GetFields();
            IDictionary<String, PdfFormField> fields = new LinkedDictionary<String, PdfFormField>();
            PdfArray shouldBeRemoved = new PdfArray();
            foreach (PdfObject field in rawFields) {
                if (field.IsFlushed()) {
                    LOGGER.LogInformation(FormsLogMessageConstants.FORM_FIELD_WAS_FLUSHED);
                    continue;
                }
                PdfFormField formField = PdfFormField.MakeFormField(field, document);
                if (formField == null) {
                    // Pure annotation can't be in AcroForm dictionary
                    // Ok, let's just skip them, they were (will be) processed with their parents if any
                    LOGGER.LogWarning(FormsLogMessageConstants.ANNOTATION_IN_ACROFORM_DICTIONARY);
                    continue;
                }
                PdfFormFieldMergeUtil.MergeKidsWithSameNames(formField, false);
                PdfString fieldName = formField.GetFieldName();
                if (fieldName != null) {
                    String name = formField.GetFieldName().ToUnicodeString();
                    if (formField.IsInReadingMode() || !fields.ContainsKey(name) || !PdfFormFieldMergeUtil.MergeTwoFieldsWithTheSameNames
                        (fields.Get(name), formField, true)) {
                        fields.Put(formField.GetFieldName().ToUnicodeString(), formField);
                    }
                    else {
                        shouldBeRemoved.Add(field);
                    }
                }
            }
            foreach (PdfObject field in shouldBeRemoved) {
                rawFields.Remove(field);
            }
            return fields;
        }

        private void RemoveFieldFromParentAndAcroForm(PdfArray formFields, PdfDictionary fieldObject) {
            formFields.Remove(fieldObject);
            PdfDictionary parent = fieldObject.GetAsDictionary(PdfName.Parent);
            if (parent != null) {
                PdfArray kids = parent.GetAsArray(PdfName.Kids);
                if (kids == null) {
                    formFields.Remove(parent);
                }
                else {
                    kids.Remove(fieldObject);
                    if (kids.IsEmpty()) {
                        RemoveFieldFromParentAndAcroForm(formFields, parent);
                    }
                }
            }
        }

        private void ProcessKids(PdfFormField field, PdfPage page) {
            PdfArray kids = field.GetKids();
            if (kids == null) {
                return;
            }
            if (kids.Size() == 1) {
                PdfDictionary kidDict = (PdfDictionary)kids.Get(0);
                PdfName type = kidDict.GetAsName(PdfName.Subtype);
                if (PdfName.Widget.Equals(type)) {
                    if (PdfFormAnnotationUtil.IsPureWidget(kidDict)) {
                        // kid is not merged field with widget
                        PdfFormAnnotationUtil.MergeWidgetWithParentField(field);
                        DefineWidgetPageAndAddToIt(page, field.GetPdfObject(), true);
                    }
                    else {
                        DefineWidgetPageAndAddToIt(page, kidDict, true);
                    }
                    return;
                }
            }
            foreach (AbstractPdfFormField child in field.GetChildFields()) {
                if (PdfFormAnnotationUtil.IsPureWidgetOrMergedField(child.GetPdfObject())) {
                    DefineWidgetPageAndAddToIt(page, child.GetPdfObject(), true);
                }
                else {
                    if (child is PdfFormField) {
                        ProcessKids((PdfFormField)child, page);
                    }
                }
            }
        }

        private void DefineWidgetPageAndAddToIt(PdfPage currentPage, PdfDictionary mergedFieldAndWidget, bool warnIfPageFlushed
            ) {
            PdfAnnotation annot = PdfAnnotation.MakeAnnotation(mergedFieldAndWidget);
            PdfPage page = GetFieldPage(mergedFieldAndWidget);
            if (page != null) {
                PdfFormAnnotationUtil.AddWidgetAnnotationToPage(page, annot);
                return;
            }
            PdfDictionary pageDic = annot.GetPageObject();
            if (pageDic == null) {
                PdfFormAnnotationUtil.AddWidgetAnnotationToPage(currentPage, annot);
            }
            else {
                if (warnIfPageFlushed && pageDic.IsFlushed()) {
                    throw new PdfException(FormsExceptionMessageConstant.PAGE_ALREADY_FLUSHED_USE_ADD_FIELD_APPEARANCE_TO_PAGE_METHOD_BEFORE_PAGE_FLUSHING
                        );
                }
                PdfDocument doc = pageDic.GetIndirectReference().GetDocument();
                PdfPage widgetPage = doc.GetPage(pageDic);
                PdfFormAnnotationUtil.AddWidgetAnnotationToPage(widgetPage == null ? currentPage : widgetPage, annot);
            }
        }

        /// <summary>Determines whether the AcroForm contains XFA data.</summary>
        /// <returns>a boolean</returns>
        public virtual bool HasXfaForm() {
            return xfaForm != null && xfaForm.IsXfaPresent();
        }

        /// <summary>
        /// Gets the
        /// <see cref="iText.Forms.Xfa.XfaForm"/>
        /// atribute.
        /// </summary>
        /// <returns>the XFA form object</returns>
        public virtual XfaForm GetXfaForm() {
            return xfaForm;
        }

        /// <summary>Removes the XFA stream from the document.</summary>
        public virtual void RemoveXfaForm() {
            if (HasXfaForm()) {
                PdfDictionary root = document.GetCatalog().GetPdfObject();
                PdfDictionary acroform = root.GetAsDictionary(PdfName.AcroForm);
                acroform.Remove(PdfName.XFA);
                xfaForm = null;
            }
        }

        /// <summary>Put a key/value pair in the dictionary and overwrite previous value if it already exists.</summary>
        /// <param name="key">the key as pdf name</param>
        /// <param name="value">the value as pdf object</param>
        /// <returns>
        /// this
        /// <see cref="PdfAcroForm"/>
        /// instance
        /// </returns>
        public virtual iText.Forms.PdfAcroForm Put(PdfName key, PdfObject value) {
            GetPdfObject().Put(key, value);
            SetModified();
            return this;
        }

        /// <summary>Releases underlying pdf object and other pdf entities used by wrapper.</summary>
        /// <remarks>
        /// Releases underlying pdf object and other pdf entities used by wrapper.
        /// This method should be called instead of direct call to
        /// <see cref="iText.Kernel.Pdf.PdfObject.Release()"/>
        /// if the wrapper is used.
        /// </remarks>
        public virtual void Release() {
            UnsetForbidRelease();
            GetPdfObject().Release();
            if (fields != null) {
                foreach (PdfFormField field in fields.Values) {
                    field.Release();
                }
                fields.Clear();
                fields = null;
            }
        }

        public override PdfObjectWrapper<PdfDictionary> SetModified() {
            if (GetPdfObject().GetIndirectReference() != null) {
                base.SetModified();
            }
            else {
                document.GetCatalog().SetModified();
            }
            return this;
        }

        private static PdfDictionary CreateAcroFormDictionaryByFields(PdfArray fields) {
            PdfDictionary dictionary = new PdfDictionary();
            dictionary.Put(PdfName.Fields, fields);
            return dictionary;
        }

        private PdfPage GetFieldPage(PdfDictionary annotDict) {
            PdfDictionary pageDic = annotDict.GetAsDictionary(PdfName.P);
            if (pageDic != null) {
                return document.GetPage(pageDic);
            }
            for (int i = 1; i <= document.GetNumberOfPages(); i++) {
                PdfPage page = document.GetPage(i);
                if (!page.IsFlushed()) {
                    PdfAnnotation annotation = PdfAnnotation.MakeAnnotation(annotDict);
                    if (annotation != null && page.ContainsAnnotation(annotation)) {
                        return page;
                    }
                }
            }
            return null;
        }

        private ICollection<PdfFormField> PrepareFieldsForFlattening(PdfFormField field) {
            ICollection<PdfFormField> preparedFields = new LinkedHashSet<PdfFormField>();
            preparedFields.Add(field);
            foreach (PdfFormField child in field.GetChildFormFields()) {
                preparedFields.AddAll(PrepareFieldsForFlattening(child));
            }
            return preparedFields;
        }

        private ICollection<PdfFormField> GetAllFormFieldsWithoutNames() {
            if (fields.IsEmpty()) {
                fields = PopulateFormFieldsMap();
            }
            ICollection<PdfFormField> allFields = new LinkedHashSet<PdfFormField>();
            foreach (KeyValuePair<String, PdfFormField> field in fields) {
                allFields.Add(field.Value);
                IList<PdfFormField> kids = field.Value.GetAllChildFormFields();
                allFields.AddAll(kids);
            }
            return allFields;
        }

        private bool NeedToAddToAcroform(PdfFormField field, bool throwExceptionOnError) {
            String fieldNameBeforeMergeCall = field.GetFieldName().ToUnicodeString();
            if (!fields.ContainsKey(fieldNameBeforeMergeCall)) {
                return true;
            }
            if (!PdfFormFieldMergeUtil.MergeTwoFieldsWithTheSameNames(fields.Get(fieldNameBeforeMergeCall), field, throwExceptionOnError
                )) {
                return true;
            }
            bool isFieldNameChanged = !fieldNameBeforeMergeCall.Equals(field.GetFieldName().ToUnicodeString());
            return isFieldNameChanged;
        }
    }
}
